package com.specter.mvc.model.dao.impl;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.jdbc.SqlRunner;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.specter.mvc.model.Page;
import com.specter.mvc.model.Request;
import com.specter.mvc.model.batis.JPAEntityRegistry;
import com.specter.mvc.model.dao.IbatisBaseDao;
import com.specter.utils.BeanUtils;

/**
 * @author Liang.Wang
 * @since 2017.1.12
 * 
 */
@Repository
public class IBatisGenericDao extends SqlSessionDaoSupport implements IbatisBaseDao {

	@Override
	@Autowired
	public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
		super.setSqlSessionFactory(sqlSessionFactory);
	}

	private static final String INSERT = "_insert";// 插入值无ID
	private static final String DELETE = "_delete";// 按照ID删除
	private static final String UPDATE = "_update";// 按照ID更新
	private static final String SELECT = "_select";// 按照ID查询

	private static final String DELETE_MAP = "_delete_by_map";// 按照属性值'等'删除
	private static final String UPDATE_MAP = "_update_by_map";// 按照ID只更新有值对象

	private static final String SELECT_INS = "_select_by_ins";// 按照多ID值'等'查询列表
	private static final String SELECT_MAP = "_select_by_map";// 按照属性值'等'查询列表
	private static final String SELECT_PAG = "_select_by_pag";// 按照属性值'等'分页查询

	@Override
	public Object template() {
		return getSqlSession();
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T sql(String sql, Object... params) {
		Object obj = null;
		SqlRunner sr = new SqlRunner(getSqlSession().getConnection());
		try { // 这里不用关闭connection由框架自动管理
			if (sql.toUpperCase().startsWith("INSERT")) {
				obj = sr.insert(sql, params);

			} else if (sql.toUpperCase().startsWith("UPDATE")) {
				obj = sr.update(sql, params);

			} else if (sql.toUpperCase().startsWith("SELECT")) {
				obj = sr.selectAll(sql, params);

			} else {
				throw new Exception("Not Supported!");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return (T) obj;
	}

	@Override
	public boolean insert(Class<?> clazz, String stmt, Object params) {
		int i = getSqlSession().insert(buildStmt(clazz, stmt), params);
		return i >= 0;
	}

	@Override
	public <T> boolean insert(T entity) {
		int i = getSqlSession().insert(buildStmt(entity.getClass(), INSERT), entity);
		return i >= 0;
	}

	@Override
	public boolean insertBatch(List<?> entities, int size) {
		String statement = buildStmt(entities.get(0).getClass(), INSERT);
		return insertBatch(statement, entities, size);
	}

	public boolean insertBatch(String stmt, List<?> params, int size) {
		SqlSession batch = this.batch();
		for (int i = 0; i < params.size(); i++) {
			batch.insert(buildStmt(null, stmt), params.get(i));
			if ((i + 1) % size == 0) {
				batch.flushStatements();
			}
		}
		batch.flushStatements();
		return true;
	}

	@Override
	public <T> boolean insertOrUpdate(T entity) {
		Serializable id = JPAEntityRegistry.getIdValue(entity);
		// 根据注解找到主键字段，判断主键字段是否为空
		return id == null ? insert(entity) : update(entity, true);
	}

	@Override
	public boolean insertOrUpdate(List<?> entities) {
		return insertOrUpdate(entities, 30);
	}

	@Override
	public boolean insertOrUpdate(List<?> entities, int size) {
		String insertStmt = buildStmt(entities.get(0).getClass(), INSERT);
		String updateStmt = buildStmt(entities.get(0).getClass(), UPDATE);
		SqlSession batch = this.batch();
		for (int i = 0; i < entities.size(); i++) {
			Serializable id = JPAEntityRegistry.getIdValue(entities.get(i));
			if (id == null) {
				batch.insert(insertStmt, entities.get(i));
			} else {
				batch.update(updateStmt, entities.get(i));
			}

			if ((i + 1) % size == 0) {
				batch.flushStatements();
			}
		}
		batch.flushStatements();
		return true;
	}

	@Override
	public boolean delete(Class<?> clazz, String stmt, Object params) {
		int i = getSqlSession().delete(buildStmt(clazz, stmt), params);
		return i >= 0;
	}

	@Override
	public <T> boolean delete(T entity) {
		int i = getSqlSession().delete(buildStmt(entity.getClass(), DELETE_MAP), entity);
		return i >= 0;
	}

	@Override
	public boolean deleteById(Class<?> clazz, Serializable id) {
		int i = getSqlSession().delete(buildStmt(clazz, DELETE), id);
		return i >= 0;
	}

	@Override
	public boolean deleteById(Class<?> clazz, List<? extends Serializable> ids) {
		String stm = buildStmt(clazz, DELETE);
		SqlSession batch = this.batch();
		for (int i = 0; i < ids.size(); i++) {
			batch.delete(stm, ids.get(i));
			if ((i + 1) % 30 == 0) {
				batch.flushStatements();
			}
		}
		batch.flushStatements();
		return true;
	}

	@Override
	public boolean update(Class<?> clazz, String stmt, Object params) {
		int i = getSqlSession().update(buildStmt(clazz, stmt), params);
		return i >= 0;
	}

	@Override
	public <T> boolean update(T entity) {
		return this.update(entity, true);
	}

	@Override
	public <T> boolean update(T entity, boolean isBlank) {
		String stmt = isBlank ? UPDATE : UPDATE_MAP;
		int i = getSqlSession().update(buildStmt(entity.getClass(), stmt), entity);
		return i >= 0;
	}

	@Override
	public <T> boolean update(List<T> entities) {
		return this.update(entities, true);
	}

	@Override
	public <T> boolean update(List<T> entities, boolean isBlank) {
		String stmt = isBlank ? UPDATE : UPDATE_MAP;
		stmt = buildStmt(entities.get(0).getClass(), stmt);
		SqlSession batch = this.batch();
		for (int i = 0; i < entities.size(); i++) {
			batch.update(stmt, entities.get(i));
			if ((i + 1) % 30 == 0) {
				batch.flushStatements();
			}
		}
		batch.flushStatements();
		return true;
	}

	@Override
	public boolean updateById(Class<?> clazz, Serializable id, Object... params) {
		Map<Object, Object> map = new HashMap<>();
		for (int i = 0; i < params.length - 1; i += 2) {
			map.put(params[i], params[i + 1]);
		}
		map.put(JPAEntityRegistry.getIdName(clazz), id);
		int i = getSqlSession().update(buildStmt(clazz, UPDATE_MAP), map);
		return i >= 0;
	}

	public boolean updateBatch(String stmt, List<?> params, int size) {
		SqlSession batch = this.batch();
		for (int i = 0; i < params.size(); i++) {
			batch.update(buildStmt(null, stmt), params.get(i));
			if ((i + 1) % size == 0) {
				batch.flushStatements();
			}
		}
		batch.flushStatements();
		return true;
	}

	@Override
	public <T> T selectOne(Class<T> clazz, String stmt, Object params) {
		return getSqlSession().selectOne(buildStmt(clazz, stmt), params);
	}

	@Override
	public <T> T selectOne(T entity) {
		return getSqlSession().selectOne(buildStmt(entity.getClass(), SELECT_MAP), entity);
	}

	@Override
	public <T> T selectOneById(Class<T> clazz, Serializable id) {
		return getSqlSession().selectOne(buildStmt(clazz, SELECT), id);
	}

	@Override
	public <T> T selectOneByIt(Class<T> clazz, Object... params) {
		Map<Object, Object> map = new HashMap<>();
		for (int i = 0; i < params.length - 1; i += 2) {
			map.put(params[i], params[i + 1]);
		}
		return getSqlSession().selectOne(buildStmt(clazz, SELECT_MAP), map);
	}

	@Override
	public <T> List<T> select(Class<T> clazz, String stmt, Object params) {
		return getSqlSession().selectList(buildStmt(clazz, stmt), params);
	}

	@Override
	public <T> List<T> select(T entity, Object orders) {
		Map<String, Object> params = BeanUtils.obj2Map(entity);
		params.put("orders_", orders);
		return getSqlSession().selectList(buildStmt(entity.getClass(), SELECT_MAP), params);
	}

	@Override
	public <T> List<T> select(Class<T> clazz, Object params) {
		return getSqlSession().selectList(buildStmt(clazz, SELECT_MAP), params);
	}

	@Override
	public <T> List<T> selectById(Class<T> clazz, List<? extends Serializable> ids, Object orders) {
		Map<String, Object> params = new HashMap<>();
		params.put("list", ids);
		params.put("orders_", orders);
		// 使用in查询
		return getSqlSession().selectList(buildStmt(clazz, SELECT_INS), params);
	}

	@Override
	public <T> List<T> selectByIt(Class<T> clazz, Object... params) {
		Map<Object, Object> map = new HashMap<>();
		for (int i = 0; i < params.length - 1; i += 2) {
			map.put(params[i], params[i + 1]);
		}
		return getSqlSession().selectList(buildStmt(clazz, SELECT_MAP), map);
	}

	@Override
	public <T> Page<T> selectPage(Class<T> clazz, String stmt, Request request) {
		// 如果 Request.QueryType.COUNT 时，下面的语句将被阻止执行;
		List<T> rows = getSqlSession().selectList(buildStmt(clazz, stmt), request);

		return new Page<>(request.getPageno(), request.getLimit(), request.getRowCount(), rows);
	}

	@Override
	public <T> Page<T> selectPage(Class<T> clazz, Request request) {
		// 如果 Request.QueryType.COUNT 时，下面的语句将被阻止执行;
		List<T> rows = getSqlSession().selectList(buildStmt(clazz, SELECT_PAG), request);

		return new Page<>(request.getPageno(), request.getLimit(), request.getRowCount(), rows);
	}

	/**
	 * 构建SQL语句的标识
	 */
	private String buildStmt(Class<?> clazz, String stmtName) {
		StringBuilder sb = new StringBuilder();
		if (stmtName == null) {
			return sb.toString();
		}
		// 只要名称中含"."，则认为是全路径
		if (!stmtName.contains(".")) {
			String prefix = clazz == null ? "" : clazz.getName();
			sb.append(prefix);
			sb.append(".");
		}
		// 添加数据库类型后缀 直接databaseId进行识别
		sb.append(stmtName);
		return sb.toString();
	}

	protected SqlSession batch() {
		return getSqlSessionFactory().openSession(ExecutorType.BATCH);
	}
}
